﻿// Inner Fire 游戏引擎库
// DirectX主类分装 - 更新
//
// Copyright (c) 2023 by 尔西大帝. All rights reserved.
//
// 用于更新的项目
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-24

#include <dx_graphics.h>
#include <dx_scene/dx_scene.h>

namespace ifire {
void DxGraphics::Scene_OnKeyboardInput() { scene_->OnKeyboardInput(); }
void DxGraphics::Scene_OnMouseMove(WPARAM state, int x, int y) {scene_->OnMouseMove(state, x, y);}
void DxGraphics::Scene_OnMouseUp(WPARAM state, int x, int y) {scene_->OnMouseUp(state, x, y);}
void DxGraphics::Scene_OnMouseDown(WPARAM state, int x, int y) {scene_->OnMouseDown(state, x, y);}

void DxGraphics::Update() {
  UpdateCamera_OnKeyboardInput();
  Scene_OnKeyboardInput();

  if (camera_type_ == CameraType::SPHERICAL) {
    spherical_camera_.UpdateViewMatrix();
    eye_pos_ = spherical_camera_.GetPosition();
    XMStoreFloat4x4(&view_, spherical_camera_.GetViewMatrix());
  } else {
    camera_.UpdateViewMatrix();
    XMStoreFloat4x4(&view_, camera_.GetView());
    XMStoreFloat4x4(&projection_, camera_.GetProj());
  }

  SwitchFrameResource();

  scene_->Update();
}

void DxGraphics::UpdateCamera_OnKeyboardInput() {
  const float dt = g_timer.fixedDeltaTime;
  if (camera_type_ == CameraType::NORMAL) {
    if (GetAsyncKeyState('W') & 0x8000)
      camera_.Walk(10.0f * dt);

    if (GetAsyncKeyState('S') & 0x8000)
      camera_.Walk(-10.0f * dt);

    if (GetAsyncKeyState('A') & 0x8000)
      camera_.Strafe(-10.0f * dt);

    if (GetAsyncKeyState('D') & 0x8000)
      camera_.Strafe(10.0f * dt);
  }
}

void DxGraphics::UpdateCamera_OnMouseMove(WPARAM state, int x, int y) {
  if (camera_type_ == CameraType::SPHERICAL) {
    // 得到位移
    float move_x = static_cast<float>(x - last_mouse_pos_.x);
    float move_y = static_cast<float>(y - last_mouse_pos_.y);

    if ((state & MK_LBUTTON) != 0) {
      spherical_camera_.OnRotate(move_x, move_y);
    } else if ((state & MK_RBUTTON) != 0) {
      spherical_camera_.OnZoom(move_x, move_y);
    }

  } else if (camera_type_ == CameraType::NORMAL) {
    if ((state & MK_LBUTTON) != 0) {
      // Make each pixel correspond to a quarter of a degree.
      float dx = 0.25f * static_cast<float>(x - last_mouse_pos_.x);
      float dy = 0.25f * static_cast<float>(y - last_mouse_pos_.y);

      camera_.Pitch(XMConvertToRadians(dy));
      camera_.RotateY(XMConvertToRadians(dx));
    }
  }

  // 记录最后
  last_mouse_pos_.x = x;
  last_mouse_pos_.y = y;
}

void DxGraphics::UpdateCamera_OnMouseDown(WPARAM state, int x, int y) {
  if (camera_type_ == CameraType::SPHERICAL) {
    last_mouse_pos_.x = x;
    last_mouse_pos_.y = y;
    SetCapture(hwnd_);
  }
}

void DxGraphics::UpdateCamera_ReleaseCapture() {
  if (camera_type_ == CameraType::SPHERICAL) {
    ReleaseCapture();
  }
}

void DxGraphics::SwitchFrameResource() {
  // 循环往复地获得帧资源循环数组中的元素
  frame_resource_index_ = (frame_resource_index_ + 1) % FRAME_RESOURCE_COUNT;
  current_frame_resource_ = frame_resources_[frame_resource_index_].get();

  // 当前的这个帧资源是否还有未执行完的命令？如果有，则令其执行到这个围栏点
  if (current_frame_resource_->Fence != 0 &&
      fence_->GetCompletedValue() < current_frame_resource_->Fence) {
    HANDLE handle = CreateEventEx(nullptr, NULL, false, EVENT_ALL_ACCESS);
    HASSERT(
        fence_->SetEventOnCompletion(current_frame_resource_->Fence, handle));
    WaitForSingleObject(handle, INFINITE);
    CloseHandle(handle);
  }
}

void DxGraphics::UpdateProjection() {
  XMMATRIX projection = XMMatrixPerspectiveFovLH(
      XMConvertToRadians(60.0f), GetAspectRatio(), 1.0f, 1000.0f);
  XMStoreFloat4x4(&projection_, projection);
}

void DxGraphics::UpdateObjectCBs() {
  // 得到BUFFER对象
  auto obj_buffer = current_frame_resource_->ObjectCB.get();
  // 遍历每个游戏对象，设置他们的信息
  for (auto& e : items_) {
    // 仅当项目已经发生变化时才设置
    if (e->NumFramesDirty > 0) {
      // 设置世界矩阵
      XMMATRIX world = XMLoadFloat4x4(&e->World);
      XMMATRIX tex_transform = XMLoadFloat4x4(&e->TexTransform);

      ObjectConstants obj_constants;
      XMStoreFloat4x4(&obj_constants.world, XMMatrixTranspose(world));
      XMStoreFloat4x4(&obj_constants.tex_transform, //
          XMMatrixTranspose(tex_transform));
      obj_constants.DisplacementMapTexelSize = e->DisplacementMapTexelSize;
      obj_constants.GridSpatialStep = e->GridSpatialStep;

      // 需要复制到object_buffer_index指针的位置
      obj_buffer->CopyData(e->ObjCBIndex, obj_constants);

      // 其它的帧同样也要更新
      e->NumFramesDirty--;
    }
  }
}

void DxGraphics::UpdateMaterialCBs() {
  // 得到BUFFER对象
  auto material_buffer = current_frame_resource_->MaterialCB.get();
  // 遍历每个游戏对象，设置他们的信息
  for (auto& e : materials_) {
    // 得到值
    Material* mat = e.second.get();

    // 仅当项目已经发生变化时才设置
    if (mat->NumFramesDirty > 0) {
      XMMATRIX mat_transform = XMLoadFloat4x4(&mat->MatTransform);

      MaterialConstants mat_constants;
      mat_constants.diffuse_albedo = mat->DiffuseAlbedo;
      mat_constants.fresnel_r0 = mat->FresnelR0;
      mat_constants.roughness = mat->Roughness;
      XMStoreFloat4x4(&mat_constants.mat_transform, //
          XMMatrixTranspose(mat_transform));

      // 需要复制到cb_index指针的位置
      material_buffer->CopyData(mat->MatCBIndex, mat_constants);

      // 其它的帧同样也要更新
      mat->NumFramesDirty--;
    }
  }
}

void DxGraphics::UpdateMainPassCB() {
  XMMATRIX view = XMLoadFloat4x4(&view_);
  auto det_view = XMMatrixDeterminant(view);
  XMMATRIX inv_view = XMMatrixInverse(&det_view, view);

  XMMATRIX proj = XMLoadFloat4x4(&projection_);
  auto det_proj = XMMatrixDeterminant(proj);
  XMMATRIX inv_proj = XMMatrixInverse(&det_proj, proj);

  XMMATRIX view_proj = XMMatrixMultiply(view, proj);
  auto det_view_proj = XMMatrixDeterminant(view_proj);
  XMMATRIX inv_view_proj = XMMatrixInverse(&det_view_proj, view_proj);

  XMStoreFloat4x4(&main_pass_.View, XMMatrixTranspose(view));
  XMStoreFloat4x4(&main_pass_.InvView, XMMatrixTranspose(inv_view));
  XMStoreFloat4x4(&main_pass_.Proj, XMMatrixTranspose(proj));
  XMStoreFloat4x4(&main_pass_.InvProj, XMMatrixTranspose(inv_proj));
  XMStoreFloat4x4(&main_pass_.ViewProj, XMMatrixTranspose(view_proj));
  XMStoreFloat4x4(&main_pass_.InvViewProj, XMMatrixTranspose(inv_view_proj));
  main_pass_.EyePosW = eye_pos_;
  main_pass_.RenderTargetSize =
      XMFLOAT2((float)client_width_, (float)client_height_);
  main_pass_.InvRenderTargetSize =
      XMFLOAT2(1.0f / client_width_, 1.0f / client_height_);
  main_pass_.NearZ = 1.0f;
  main_pass_.FarZ = 1000.0f;
  main_pass_.TotalTime = g_timer.TotalTime();
  main_pass_.DeltaTime = g_timer.DeltaTime();

  // 更新雾效
  // 因为不会每帧改变，所以这里一直是默认值。

  // 更新光照
  main_pass_.AmbientLight = {0.25f, 0.25f, 0.35f, 1.0f};
  main_pass_.Lights[0].Direction = {0.57735f, -0.57735f, 0.57735f};
  main_pass_.Lights[0].Strength = {0.6f, 0.6f, 0.6f};
  main_pass_.Lights[1].Direction = {-0.57735f, -0.57735f, 0.57735f};
  main_pass_.Lights[1].Strength = {0.3f, 0.3f, 0.3f};
  main_pass_.Lights[2].Direction = {0.0f, -0.707f, -0.707f};
  main_pass_.Lights[2].Strength = {0.15f, 0.15f, 0.15f};

  auto currPassCB = current_frame_resource_->PassCB.get();
  currPassCB->CopyData(0, main_pass_);
}

void DxGraphics::UpdateReflectedPassCB() {
  reflected_pass_ = main_pass_;

  XMVECTOR mirrorPlane = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
  XMMATRIX R = XMMatrixReflect(mirrorPlane);

  // 反射光源
  for (int i = 0; i < 3; ++i) {
    XMVECTOR lightDir = XMLoadFloat3(&main_pass_.Lights[i].Direction);
    XMVECTOR reflectedLightDir = XMVector3TransformNormal(lightDir, R);
    XMStoreFloat3(&reflected_pass_.Lights[i].Direction, reflectedLightDir);
  }

  // 将PASS存在索引1（MainPass是0）
  auto currPassCB = current_frame_resource_->PassCB.get();
  currPassCB->CopyData(1, reflected_pass_);
}

void DxGraphics::AnimateMaterials() {
  // Scroll the water material texture coordinates.
  auto waterMat = materials_["Water"].get();

  float& tu = waterMat->MatTransform(3, 0);
  float& tv = waterMat->MatTransform(3, 1);

  tu += 0.1f * g_timer.fixedDeltaTime;
  tv += 0.02f * g_timer.fixedDeltaTime;

  if (tu >= 1.0f)
    tu -= 1.0f;

  if (tv >= 1.0f)
    tv -= 1.0f;

  waterMat->MatTransform(3, 0) = tu;
  waterMat->MatTransform(3, 1) = tv;

  // Material has changed, so need to update cbuffer.
  waterMat->NumFramesDirty = FRAME_RESOURCE_COUNT;
}

void DxGraphics::UpdateObjectConstants(XMMATRIX mvp) {
  ObjectConstants objs;
  // 因为C++使用的是Row-Major，而HLSL使用的是Column-Major，所以需要进行转置。
  XMStoreFloat4x4(&objs.world, XMMatrixTranspose(mvp));
  obj_constants_->CopyData(0, objs);
}

void DxGraphics::UpdateSun(float theta, float phi) {
  sum_theta_ = theta;
  sum_phi_ = phi;
}

void DxGraphics::UpdateWaves() {
  // 每1/4秒就生成一个随机波浪
  static float t_base = 0.0f;
  if ((g_timer.TotalTime() - t_base) >= 0.25f) {
    t_base += 0.25f;
    int i = ikit::Rand(4, waves_->RowCount() - 5);
    int j = ikit::Rand(4, waves_->ColumnCount() - 5);
    float r = ikit::Randf(0.2f, 0.2f);
    waves_->Disturb(i, j, r);
  }

  // 更新模拟的波浪
  waves_->Update(g_timer.fixedDeltaTime);

  // 用波浪方程求出的新数据来更新波浪顶点缓冲区
  auto current_waves_vb = current_frame_resource_->WavesVB.get();
  for (int i = 0; i < waves_->VertexCount(); i++) {
    Vertex v;
    v.Pos = waves_->Position(i);
    v.Normal = waves_->Normal(i);
    // Derive tex-coords from position by
    // mapping [-w/2,w/2] --> [0,1]
    v.TexC.x = 0.5f + v.Pos.x / waves_->Width();
    v.TexC.y = 0.5f - v.Pos.z / waves_->Depth();

    current_waves_vb->CopyData(i, v);
  }

  // 将波浪渲染项的动态顶点缓冲区设置到当前帧的顶点缓冲区
  waves_item_->Geo->VertexBufferGPU = current_waves_vb->Resource();
}

void DxGraphics::OnKeyboardInput_SumLight() {
  const float dt = g_timer.fixedDeltaTime;

  if (GetAsyncKeyState(VK_LEFT) & 0x8000) {
    sum_theta_ -= 1.0f * dt;
  }

  if (GetAsyncKeyState(VK_RIGHT) & 0x8000) {
    sum_theta_ += 1.0f * dt;
  }

  if (GetAsyncKeyState(VK_UP) & 0x8000) {
    sum_phi_ -= 1.0f * dt;
  }

  if (GetAsyncKeyState(VK_DOWN) & 0x8000) {
    sum_phi_ += 1.0f * dt;
  }

  sum_phi_ = MathHelper::Clamp(sum_phi_, 0.1f, XM_PIDIV2);
}

void DxGraphics::OnKeyboardInput_Room() {
  //
  // Allow user to move skull.
  //
  const float dt = g_timer.fixedDeltaTime;

  if (GetAsyncKeyState('A') & 0x8000)
    skull_translation.x -= 1.0f * dt;

  if (GetAsyncKeyState('D') & 0x8000)
    skull_translation.x += 1.0f * dt;

  if (GetAsyncKeyState('W') & 0x8000)
    skull_translation.y += 1.0f * dt;

  if (GetAsyncKeyState('S') & 0x8000)
    skull_translation.y -= 1.0f * dt;

  // Don't let user move below ground plane.
  skull_translation.y = MathHelper::Max(skull_translation.y, 0.0f);

  // Update the new world matrix.
  XMMATRIX skullRotate = XMMatrixRotationY(0.5f * MathHelper::Pi);
  XMMATRIX skullScale = XMMatrixScaling(0.45f, 0.45f, 0.45f);
  XMMATRIX skullOffset = XMMatrixTranslation(
      skull_translation.x, skull_translation.y, skull_translation.z);
  XMMATRIX skullWorld = skullRotate * skullScale * skullOffset;
  XMStoreFloat4x4(&skull_item_->World, skullWorld);

  // Update reflection world matrix.
  XMVECTOR mirrorPlane = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
  XMMATRIX R = XMMatrixReflect(mirrorPlane);
  XMStoreFloat4x4(&reflected_skull_item_->World, skullWorld * R);
  // 这个地板是不动的，所以不需要其它矩阵
  XMStoreFloat4x4(&reflected_floor_item_->World, R);

  // Update shadow world matrix.
  XMVECTOR shadowPlane = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); // xz plane
  XMVECTOR toMainLight = -XMLoadFloat3(&main_pass_.Lights[0].Direction);
  XMMATRIX S = XMMatrixShadow(shadowPlane, toMainLight);
  XMMATRIX shadowOffsetY = XMMatrixTranslation(0.0f, 0.001f, 0.0f);
  XMStoreFloat4x4(&shadowed_skull_item_->World, skullWorld * S * shadowOffsetY);

  skull_item_->NumFramesDirty = FRAME_RESOURCE_COUNT;
  reflected_skull_item_->NumFramesDirty = FRAME_RESOURCE_COUNT;
  shadowed_skull_item_->NumFramesDirty = FRAME_RESOURCE_COUNT;
  reflected_floor_item_->NumFramesDirty = FRAME_RESOURCE_COUNT;
}

} // namespace ifire